1 module hip.net.backend.tcp; 2 import hip.network; 3 import hip.api.net.hipnet; 4 5 version(WebAssembly){} 6 else: 7 import std.socket; 8 9 /** 10 * Abstracts away different behaviors from Windows - Posix. 11 * Posix throws an exception, while Windows return a client which is not alive. 12 * Params: 13 * host = The socket which will accept 14 * Returns: Accepted socket or null. 15 */ 16 Socket accept2(Socket host) 17 { 18 Socket ret; 19 try 20 { 21 ret = host.accept(); 22 if(ret is null || !ret.isAlive) 23 return null; 24 ret.blocking = false; 25 } 26 catch (SocketAcceptException e) ///This error is always thrown by Linux if it is not blocking 27 return null; 28 return ret; 29 } 30 31 class TCPNetwork : INetworkBackend 32 { 33 34 Socket hostSocket; 35 NetConnectStatus _status; 36 Socket connectSocket; 37 Socket client; 38 SocketSet readSet, writeSet, errSet; 39 void delegate() onConnect; 40 41 /** 42 * It is useful to send some messages right after connecting the socket. 43 * The problem is that sometimes, this data can't be sent. 44 * To solve that issue, this data is accumulated, and thus, is sent in either 45 * getData or sendData, the one which is called first. 46 * After that, accumulated data becomes unused. 47 */ 48 ubyte[] accumulatedData; 49 50 int connectedSockets; 51 52 this() 53 { 54 readSet = new SocketSet(); 55 writeSet = new SocketSet(); 56 errSet = new SocketSet(); 57 } 58 59 protected ptrdiff_t getEventCount() 60 { 61 import hip.util.data_structures:staticArray; 62 63 foreach(set; [readSet, writeSet, errSet].staticArray) 64 { 65 set.reset(); 66 foreach(sock; [hostSocket, connectSocket, client].staticArray) 67 { 68 if(sock !is null) 69 set.add(sock); 70 } 71 } 72 TimeVal zero; 73 return Socket.select(readSet, writeSet, null, &zero); 74 } 75 76 Socket getSocketToSendData() 77 { 78 if(hostSocket !is null) 79 return client; 80 return connectSocket; 81 } 82 83 84 NetConnectStatus host(NetIPAddress ip) 85 { 86 Socket s = new TcpSocket(); 87 hostSocket = s; 88 s.blocking = false; 89 // s.setOption(SocketOptionLevel.SOCKET, SocketOption.LINGER, 1); 90 // s.setOption(SocketOptionLevel.SOCKET, SocketOption.DEBUG, 1); 91 92 try 93 { 94 s.bind(ip.type == IPType.ipv4 ? new InternetAddress(ip.ip, ip.port) : new Internet6Address(ip.ip, ip.port)); 95 s.listen(1); 96 if(getEventCount() > 0 && readSet.isSet(s)) 97 client = s.accept2(); 98 return setStatus(client !is null ? NetConnectStatus.connected : NetConnectStatus.waiting); 99 } 100 catch (SocketOSException e) //Error when multiple bindings. Make it a client 101 { 102 hostSocket = null; 103 return _status = NetConnectStatus.disconnected; 104 } 105 } 106 107 108 bool isHost() const 109 { 110 return hostSocket !is null; 111 } 112 uint getConnectionSelfID() const { return 0; } 113 void targetConnectionID(uint ID) { } 114 uint targetConnectionID() const { return 0;} 115 116 NetConnectStatus connect(NetIPAddress ip, void delegate() onConnect, uint id = NetID.server) 117 { 118 import std.socket; 119 this.onConnect = onConnect; 120 if(hostSocket is null && connectSocket is null) 121 { 122 if(host(ip) == NetConnectStatus.disconnected) 123 { 124 Socket s = new TcpSocket(); 125 connectSocket = s; 126 s.blocking = false; 127 s.connect(ip.type == IPType.ipv4 ? new InternetAddress(ip.ip, ip.port) : new Internet6Address(ip.ip, ip.port)); 128 setStatus(!wouldHaveBlocked() ? NetConnectStatus.connected : NetConnectStatus.waiting); 129 } 130 } 131 132 if(isHost && (client is null || !client.isAlive)) 133 { 134 if(getEventCount() > 0 && readSet.isSet(hostSocket)) 135 client = hostSocket.accept2(); 136 return setStatus(client !is null ? NetConnectStatus.connected : NetConnectStatus.waiting); 137 } 138 139 if(connectSocket !is null) 140 { 141 setStatus(!wouldHaveBlocked() ? NetConnectStatus.connected : NetConnectStatus.waiting); 142 } 143 144 return status; 145 } 146 147 NetConnectStatus setStatus(NetConnectStatus stat) 148 { 149 _status = stat; 150 switch(stat) 151 { 152 case NetConnectStatus.connected: 153 if(onConnect !is null) onConnect(); 154 break; 155 default: 156 break; 157 } 158 return stat; 159 } 160 161 162 bool sendData(ubyte[] data) 163 { 164 import std.stdio; 165 if(status == NetConnectStatus.connected) 166 { 167 if(getEventCount() == 0 || !writeSet.isSet(getSocketToSendData)) 168 { 169 ///Accumulates the data if it can't be sent. 170 accumulatedData~= data.dup; 171 return false; 172 } 173 else 174 unfillBuffer(); 175 if(getSocketToSendData == client) 176 writeln("Sending ", data, " to client. "); 177 else if(getSocketToSendData == connectSocket) 178 writeln("Sending data to host "); 179 ptrdiff_t res = getSocketToSendData.send(data); 180 if(res == 0|| res == -1) //Socket closed 181 return false; 182 return true; 183 } 184 return false; 185 } 186 187 /** 188 * This function was designed to avoid calling getEventCount(). 189 * Be sure to only call that function after a getEventCount() function call. 190 */ 191 private void unfillBuffer() 192 { 193 if(accumulatedData.length == 0) 194 return; 195 if(writeSet.isSet(getSocketToSendData)) 196 { 197 import core.memory; 198 ubyte[] dataToSend = accumulatedData; 199 accumulatedData = null; 200 sendData(dataToSend); 201 GC.free(dataToSend.ptr); 202 } 203 } 204 205 size_t getData(ref ubyte[] buffer) 206 { 207 if(status == NetConnectStatus.connected) 208 { 209 Socket s = getSocketToSendData; 210 bool canRead = getEventCount() > 0 && readSet.isSet(s); 211 unfillBuffer(); 212 if(canRead) 213 { 214 ptrdiff_t received = s.receive(buffer); 215 if(received == 0) 216 { 217 _status = NetConnectStatus.attemptingReconnect; 218 } 219 if(received == -1) 220 return 0; 221 return received; 222 } 223 } 224 return 0; 225 } 226 227 void attemptReconnection() 228 { 229 if(status == NetConnectStatus.attemptingReconnect) 230 { 231 Socket s = getSocketToSendData(); 232 if(s !is null) 233 { 234 s.shutdown(SocketShutdown.BOTH); 235 s.close(); 236 } 237 if(hostSocket !is null) 238 client = null; 239 240 _status = NetConnectStatus.waiting; 241 } 242 } 243 244 void disconnect() 245 { 246 Socket s = getSocketToSendData; 247 if(s is null) 248 return; 249 s.shutdown(SocketShutdown.BOTH); 250 s.close(); 251 hostSocket = null; 252 client = null; 253 connectSocket = null; 254 _status = NetConnectStatus.disconnected; 255 } 256 257 NetConnectStatus status() const { return _status; } 258 }